/* Userspace version of pioctl
 *
 * Copyright (C) 2009 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public Licence
 * as published by the Free Software Foundation; either version
 * 2 of the Licence, or (at your option) any later version.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define syscall _X_syscall
#include <unistd.h>
#undef syscall
#include <syscall.h>
#include <keyutils.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/xattr.h>
#include <arpa/inet.h>
#include <errno.h>
#include <alloca.h>
#include <limits.h>
#include <dlfcn.h>
#include <linux/afs_ioctl.h>

#define RTLD_NEXT      ((void *) -1l)

#define O_NOACCESS	00000003
#define O_NOFOLLOW	00400000	/* don't follow links */
#define O_NODE		04000000	/* open filesystem node, not device */

#ifndef _LINUX_PIOCTL_H
struct ViceIoctl {
	caddr_t		in;
	caddr_t		out;
	short		in_size;
	short		out_size;
};

#define _VICEIOCTL(nr)		_IOW('V', nr, struct ViceIoctl)
#define _CVICEIOCTL(nr)		_IOW('C', nr, struct ViceIoctl)
#define _OVICEIOCTL(nr)		_IOW('O', nr, struct ViceIoctl)
#endif

#define AFS_PIOCTL	0x14

#define PSetTokens		3		/* set authentication tokens for user */
#define PGetVolStat		4		/* get volume status */
#define PSetVolStat		5		/* set volume status */
#define PGetTokens		8		/* get authentication tokens for user */
#define PGetFID			22		/* get file ID */
#define PGetFileCell		30		/* get the cell a file inhabits */

#define VIOCSETTOK		_VICEIOCTL(PSetTokens)
#define VIOCGETTOK		_VICEIOCTL(PGetTokens)
#define VIOCGETFID		_VICEIOCTL(PGetFID)
#define VIOC_FILE_CELL_NAME	_VICEIOCTL(PGetFileCell)
#define VIOCGETVOLSTAT		_VICEIOCTL(PGetVolStat)
#define VIOCSETVOLSTAT		_VICEIOCTL(PSetVolStat)

#define AFS_XATTR_PREFIX "system.afs."

struct afs_fid {
	unsigned	vid;		/* volume ID */
	unsigned	vnode;		/* file index within volume */
	unsigned	unique;		/* unique ID number (file index version) */
};

/*
 * User details when getting or submitting a token
 */
struct clear_token {
	uint32_t	auth_handle;		/* key version number */
	uint8_t		session_key[8];		/* session encryption key */
	uint32_t	vice_id;		/* client/user ID */
	uint32_t	begin_timestamp;	/* time_t at which ticket starts */
	uint32_t	end_timestamp;		/* time_t at which ticket expires */
};

/*
 * Version 2 payload for RxRPC key
 */
struct rxrpc_key_data_v2 {
	uint32_t	kif_version;		/* 2 */
	uint16_t	security_index;
	uint16_t	ticket_length;
	uint32_t	vice_id;
	uint32_t	start;			/* time_t */
	uint32_t	expiry;			/* time_t */
	uint32_t	kvno;
	uint8_t		session_key[8];
	uint8_t		ticket[0];
};

#define RXRPC_SECURITY_NONE	0	/* no security protocol */
#define RXRPC_SECURITY_RXKAD	2	/* kaserver or kerberos 4 */
#define RXRPC_SECURITY_RXGK	4	/* gssapi-based */

/*
 * FS volume status record
 */
struct VolumeStatus {
	int	Vid;		/* volume ID */
	int	ParentId;	/* parent volume ID */
	char	Online;		/* 1 if volume currently online and available */
	char	InService;	/* 1 if volume currently in service */
	char	Blessed;	/* same as in_service */
	char	NeedsSalvage;	/* 1 if consistency checking required */
	int	Type;		/* volume type (afs_voltype_t) */
	int	MinQuota;	/* minimum blocks set aside */
	int	MaxQuota;	/* maximum blocks this volume may occupy */
	int	BlocksInUse;	/* blocks this volume currently occupies */
	int	PartBlocksAvail;/* space available in volume's partition */
	int	PartMaxBlocks;	/* size of volume's partition */
};

#define AFS_MAXCELLNAME	64	/* maximum length of a cell name */
#define AFS_MAXVOLNAME	64	/* maximum length of a volume name */
#define AFSNAMEMAX	256	/* maximum length of a filename plus NUL */
#define AFSPATHMAX	1024	/* maximum length of a pathname plus NUL */
#define AFSOPAQUEMAX	1024	/* maximum length of an opaque field */

#define RXRPC_SECURITY_RXK5	5	/* kerberos 5 */

/*
 * AF_RXRPC key payload derived from XDR format
 * - based on openafs-1.4.10/src/auth/afs_token.xg
 */
#define AFSTOKEN_LENGTH_MAX		16384	/* max payload size */
#define AFSTOKEN_CELL_MAX		64	/* max cellname length */
#define AFSTOKEN_MAX			8	/* max tokens per payload */
#define AFSTOKEN_RK_TIX_MAX		12000	/* max RxKAD ticket size */
#define AFSTOKEN_GK_KEY_MAX		64	/* max GSSAPI key size */
#define AFSTOKEN_GK_TOKEN_MAX		16384	/* max GSSAPI token size */
#define AFSTOKEN_K5_COMPONENTS_MAX	16	/* max K5 components */
#define AFSTOKEN_K5_NAME_MAX		128	/* max K5 name length */
#define AFSTOKEN_K5_REALM_MAX		64	/* max K5 realm name length */
#define AFSTOKEN_K5_TIX_MAX		16384	/* max K5 ticket size */
#define AFSTOKEN_K5_ADDRESSES_MAX	16	/* max K5 addresses */
#define AFSTOKEN_K5_AUTHDATA_MAX	16	/* max K5 pieces of auth data */


#define dbgprintk(FMT, ...) \
	fprintf(stderr, FMT"\n", ##__VA_ARGS__)

/* make sure we maintain the format strings, even when debugging is disabled */
static inline __attribute__((format(printf, 1, 2)))
void _debug(const char *fmt, ...)
{
}

/*
 * Read the name of the root cell AFS is using
 * - rootcell must be at least 64+2 chars in size
 */
static int afs_get_root_cell(char *rootcell)
{
	FILE *f;
	char *p;

	f = fopen("/proc/fs/afs/rootcell", "r");
	if (!f)
		return -1;

	if (!fgets(rootcell, 64 + 2, f)) {
		fclose(f);
		return -1;
	}

	if (fclose(f) == EOF)
		return -1;

	p = memchr(rootcell, '\0', 64 + 2);
	if (!p || p == rootcell) {
		fprintf(stderr, "afs_get_root_cell(): Can't read root cell\n");
		errno = EIO;
		return -1;
	}

	if (p[-1] == '\n')
		*--p = '\0';

	return 0;
}

/*
 * Set a user's rxkad authentication tokens
 */
static long afs_PSetTokens(struct ViceIoctl *arg)
{
	//struct rxrpc_key_data_v2 *payload;
	struct clear_token details;
	key_serial_t keyring, key;
	const char *cp;
	uint32_t tktlen, tmp, flag;
	size_t in_size, loop, paylen;
	void *in, *in_next, *ticket, *payload;
	char rootcell[64 + 2], *cellname, *keyname, *dp;

	/* decode the argument block */
	in_next = arg->in;
	in_size = arg->in_size;

#define CHECK(n)				\
	do {					\
		if (in_size < (n))		\
			goto underflow;		\
		in = in_next;			\
		in_size -= (n);			\
		in_next += (n);			\
	} while(0)

#define DECODE(to)				\
	do {					\
		CHECK(sizeof(*(to)));		\
		memcpy(to, in, sizeof(*(to)));	\
	} while(0)

	DECODE(&tktlen);
	if (tktlen > INT_MAX)
		goto invalid;
	CHECK(tktlen);
	ticket = in;
	DECODE(&tmp);
	if (tmp != sizeof(struct clear_token))
		goto invalid;
	DECODE(&details);
	_debug("ah:%x vi:%x bts:%x ets:%x (e-b:%u)",
		details.auth_handle, details.vice_id,
		details.begin_timestamp, details.end_timestamp,
		details.end_timestamp - details.begin_timestamp);
	_debug("skey: %02x%02x%02x%02x%02x%02x%02x%02x",
		details.session_key[0], details.session_key[1],
		details.session_key[2], details.session_key[3],
		details.session_key[4], details.session_key[5],
		details.session_key[6], details.session_key[7]);
	if (details.vice_id == UINT_MAX)
		goto invalid;
	if (details.auth_handle == UINT_MAX)
		details.auth_handle = 999;

	/* flags and cellname are optional, defaulting to the root cell */
	if (in_size != 0) {
		DECODE(&flag);
		if (flag & 0x8000) {
			/* the caller wants us to give our parent a new PAG
			 * - we don't support this currently
			 */
			errno = EACCES;
			return -1;
		}

		/* remainder is cell name */
		CHECK(sizeof(char));
		cellname = in;
		for (loop = 0; loop < in_size; loop++)
			if (!isprint(cellname[loop]))
				goto invalid;

		if (cellname[loop] != '\0')
			goto invalid;

		_debug("cellname: %s", cellname);
	} else {
		if (afs_get_root_cell(rootcell) < 0)
			return -1;
		cellname = rootcell;
	}

#undef DECODE
#undef CHECK

	/* XDR encode the key */
	{
#define ROUNDUP4(x) (((x) + 3) >> 2)

		uint32_t *xdr;
		size_t cnlen;
		int xdr_len = 0;

		/* pioctl_set_token */
		xdr_len += 1;	/* flags */

		/* account for cell name */
		cnlen = strlen(cellname);
		xdr_len += 1;	/* length */
		xdr_len += ROUNDUP4(cnlen);

		/* array of token_opaque */
		xdr_len += 1;	/* count */

		/* token_opaque */
		xdr_len += 1;	/* length */

		/* afstoken_soliton */
		xdr_len += 1;	/* switch ID */

		/* token_rxkad */
		xdr_len += 2;	/* viceid, kvno */
		xdr_len += 2;	/* key */
		xdr_len += 3;	/* begintime, endtime, primary_flag */
		xdr_len += 1;	/* ticket len */
		xdr_len += ROUNDUP4(tktlen);

		paylen = xdr_len * sizeof(uint32_t);
		xdr = payload = alloca(paylen);

		/* pioctl_set_token */
		*xdr++ = htonl(0);	/* flags (bit 0 is primary cell flag) */

		/* cell name */
		*xdr++ = htonl(cnlen);	/* length */
		memcpy(xdr, cellname, cnlen);
		if (cnlen & 3)
			memset(xdr + cnlen, 0, 4 - (cnlen & 3));
		xdr += ROUNDUP4(cnlen);

		/* array of token_opaque */
		*xdr++ = htonl(1);	/* count */

		/* token_opaque */
		*xdr++ = htonl(9 * 4 + tktlen);	/* length */

		/* afstoken_soliton */
		*xdr++ = htonl(RXRPC_SECURITY_RXKAD);	/* switch ID */

		/* token_rxkad */
		*xdr++ = htonl(details.vice_id);
		*xdr++ = htonl(details.auth_handle);
		memcpy(xdr, details.session_key, 8);
		xdr += 2;
		*xdr++ = htonl(details.begin_timestamp);
		*xdr++ = htonl(details.end_timestamp);
		*xdr++ = htonl(0);	/* we don't get the primary flag here */
		*xdr++ = htonl(tktlen);
		memcpy(xdr, ticket, tktlen);
		if (tktlen & 3)
			memset(xdr + tktlen, 0, 4 - (tktlen & 3));

		_debug("afs_PSetTokens: XDR [%zu] %x,%x,%x,%x",
		       paylen,
		       ((uint32_t*) payload)[0],
		       ((uint32_t*) payload)[1],
		       ((uint32_t*) payload)[2],
		       ((uint32_t*) payload)[3]);
	}


	/* construct the key name */
	keyname = alloca(4 + strlen(cellname) + 1);
	memcpy(keyname, "afs@", 4);
	dp = keyname + 4;
	cp = cellname;
	while (*cp)
		*dp++ = toupper(*cp++);
	*dp = 0;

	/* add the key to the session keyring */
	keyring = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
	if (keyring == -1) {
		perror("PSetTokens: Keyring lookup failed");
		return -1;
	}

	/* create or update the requested key and add it to the target
	 * keyring */
	key = add_key("rxrpc", keyname, payload, paylen, keyring);
	if (key == -1) {
		perror("PSetTokens: Key create failed");
		return -1;
	}

	_debug("key serial: %x", key);
	arg->out_size = 0;
	return 0;

underflow:
	_debug("underflow");
	errno = EINVAL;
	return -1;

invalid:
	_debug("invalid arg");
	errno = EINVAL;
	return -1;
}

/*
 * Get the nth appropriate security token from the keyring
 * - limits the search to RxKAD tokens if old is true
 */
static long afs_keyring_get_nth_key(key_serial_t keyring, int nth, int old,
				    void **_payload, size_t *_plen)
{
	key_serial_t *ring, key;
	uint32_t *data = NULL, *xdr, *limit, toksz, *next;
	char *desc = NULL, *p;
	long nkeys, ntok, loop, ret, yes;

	/* read the contents of the keyring */
	nkeys = keyctl_read_alloc(keyring, (void**)&ring);
	if (nkeys == -1)
		return -1;
	nkeys /= sizeof(key_serial_t);
	if (nkeys <= 0) {
		free(ring);
		errno = EDOM;
		return -1;
	}

	/* review each key, discarding those that aren't what we're looking for
	 * or are inaccessible */
	for (loop = 0;
	     loop < nkeys;
	     loop++, free(desc), desc = NULL, free(data), data = NULL
	     ) {
		key = ring[loop];

		_debug("consider key %x", key);

		yes = 0;
		ret = keyctl_describe_alloc(key, &desc);
		if (ret == -1)
			continue;
		_debug("desc '%s'", desc);

		if (memcmp(desc, "rxrpc;", 6) != 0)
			continue;
		p = strrchr(desc, ';');
		if (!p)
			continue;
		p++;
		if (memcmp(p, "afs@", 4) != 0)
			continue;

		_debug("parse key %x", key);
		ret = keyctl_read_alloc(key, (void **) &data);
		if (ret == -1)
			continue;
		if (ret < 16 || ret & 3)
			continue;

		/* old PGetTokens can only handle security index 2 tokens */
		xdr = data;
		limit = data + (ret >> 2);

		/* skip the flags and cell name */
		xdr++;
		xdr += (ntohl(*xdr) + 4 + 3) >> 2;
		if (xdr >= limit)
			continue;

		/* get the token count */
		ntok = ntohl(*xdr++);
		if (ntok < 1 || ntok > AFSTOKEN_MAX)
			continue;
	next_token:
		if (xdr >= limit - 2)  /* there must at least be a size and a sec ix */
			continue;
		toksz = ntohl(*xdr++);
		toksz = (toksz + 3) >> 2;
		next = xdr + toksz;
		if (toksz < 2 || next > limit)
			continue;
		if (!old || ntohl(*xdr) == RXRPC_SECURITY_RXKAD)
			yes = 1;
		xdr = next;
		if (xdr < limit)
			goto next_token;
		if (xdr > limit)
			continue;
		if (!yes)
			continue;

		if (nth == 0)
			goto found;
		nth--;
	}

	_debug("return EDOM");
	free(data);
	free(desc);
	free(ring);
	errno = EDOM;
	return -1;

found:
	*_payload = data;
	*_plen = limit - data;
	free(desc);
	free(ring);
	return key;
}

/*
 * Get a user's authentication rxkad tokens
 */
static long afs_PGetTokens(struct ViceIoctl *arg)
{
	struct clear_token details;
	key_serial_t keyring, key;
	uint32_t tmp, ticket_len, primary_flag, *xdr, *next, *limit, *payload;
	size_t out_size, plen, cllen, toksz;
	void *out, *out_next;
	char rootcell[4 + 64 + 2], *cp, *cellname;
	long ret, ntok;
	int skip;

	/* find out which key we're being asked for */
	if (arg->in_size == 0) {
		skip = -1;
	} else if (arg->in_size == sizeof(skip)) {
		memcpy(&skip, arg->in, sizeof(skip));
		if (skip < 0) {
			errno = EINVAL;
			return -1;
		}
	} else {
		errno = EINVAL;
		return -1;
	}

	_debug("skip %d", skip);

	if (afs_get_root_cell(rootcell + 4) < 0)
		return -1;

	/* we're going to look through the session keyring */
	keyring = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
	if (keyring == -1) {
		perror("PGetTokens: Keyring lookup failed");
		return -1;
	}

	/* if there's no input argument, then we return the tokens for the root
	 * cell; if there is an argument, then we're being asked for the nth
	 * key belonging to this session */
	if (skip >= 0) {
		key = afs_keyring_get_nth_key(keyring, skip, 1,
					      (void **) &payload, &plen);
		if (key == -1)
			return -1;
	} else {
		/* find key for the root cell */
		memcpy(rootcell, "afs@", 4);
		for (cp = rootcell + 4; *cp; cp++)
			*cp = toupper(*cp);

		key = keyctl_search(keyring, "rxrpc", rootcell, 0);
		if (key == -1) {
			if (errno == ENOKEY)
				errno = ENOTCONN;
			else if (errno != EACCES)
				errno = EIO;
			return -1;
		}
		ret = keyctl_read_alloc(key, (void **) &payload);
		if (ret == -1)
			return -1;
		plen = ret >> 2;
	}
	_debug("payload %p [%zx]", payload, plen);

	/* find the appropriate token */
	xdr = payload;
	limit = payload + plen;
	_debug("payload [%zx]", limit - xdr);

	/* skip the flags and cell name */
	xdr++;
	cllen = ntohl(*xdr++);
	if (cllen > AFSTOKEN_CELL_MAX)
		goto bad_payload;
	cellname = (char *) xdr;
	xdr += (cllen + 3) >> 2;
	if (xdr >= limit)
		goto bad_payload;

	_debug("cellname [%zx] '%*.*s'",
		cllen, (int) cllen, (int) cllen, cellname);

	/* get the token count */
	ntok = ntohl(*xdr++);
	_debug("ntok %lx", ntok);
	if (ntok < 1 || ntok > AFSTOKEN_MAX)
		goto bad_payload;
	do {
		_debug("remain [%zx]", limit - xdr);
		if (xdr >= limit - 2)  /* there must at least be a size and a sec ix */
			goto bad_payload;
		toksz = ntohl(*xdr++);
		_debug("toksz %lx", toksz);
		toksz = (toksz + 3) >> 2;
		next = xdr + toksz;
		if (toksz < 2 || next > limit)
			goto bad_payload;
		if (ntohl(*xdr) == RXRPC_SECURITY_RXKAD)
			goto found_token;
		xdr = next;
	} while (xdr < limit);

	if (xdr > limit)
		goto bad_payload;
	free(payload);
	errno = (skip != -1) ? EPROTO : ENOTCONN;
	return -1;

found_token:
	limit = next;
	xdr++;
	_debug("found_token [%zx]", limit - xdr);
	if (limit - xdr < 8)
		goto bad_payload;
	details.vice_id		= ntohl(*xdr++);
	details.auth_handle	= ntohl(*xdr++);
	memcpy(details.session_key, xdr, 8);
	xdr += 2;
	details.begin_timestamp	= ntohl(*xdr++);
	details.end_timestamp	= ntohl(*xdr++);
	primary_flag		= ntohl(*xdr++);
	ticket_len		= ntohl(*xdr++);
	if (limit - xdr != (ticket_len + 3) >> 2)
		goto bad_payload;

	/* pass the contents of the key back to the caller */
#define CHECK(n)					\
	do {						\
		if (out_size < (n)) {			\
			errno = EINVAL;			\
			goto error_free_payload;	\
		}					\
		out = out_next;				\
		out_size -= (n);			\
		out_next += (n);			\
	} while(0)

#define ENCODE(from)					\
	do {						\
		CHECK(sizeof(*(from)));			\
		memcpy(out, from, sizeof(*(from)));	\
	} while(0)

	out_next = arg->out;
	out_size = arg->out_size;

	/* pass the ticket in at least 56 bytes of space */
	tmp = ticket_len < 56U ? ticket_len : 56U;
	ENCODE(&tmp);
	CHECK(tmp);
	memcpy(out, xdr, ticket_len);
	if (ticket_len < tmp)
		memset(out + ticket_len, 0, tmp - ticket_len);

	tmp = sizeof(details);
	ENCODE(&tmp);
	ENCODE(&details);

	/* if we were given an iterator, then there's more stuff we must
	 * return */
	if (arg->in_size > 0) {
		tmp = 0;
		if (memcmp(cellname, rootcell + 4, cllen) == 0 &&
		    rootcell[cllen + 4] == '\0')
			tmp = 1;
		ENCODE(&tmp);
		CHECK(cllen + 1);
		memcpy(out, cellname, cllen);
		((char *)out)[cllen] = 0;
	}

#undef ENCODE
#undef CHECK

	arg->out_size = (char *) out_next - arg->out;
	free(payload);
	return 0;

bad_payload:
	_debug("afs_PGetTokens: Can't parse key payload");
	errno = -EPROTO;
error_free_payload:
	free(payload);
	return -1;
}

/*
 * VIOCGETFID/PGetFID
 */
static long afs_PGetFID(int fd, int cmd, struct ViceIoctl *args)
{
	struct afs_fid *fid = (void *) args->out;
	char buf[8*3];
	int ret;

	if (args->out_size < sizeof(struct afs_fid) || !args->out) {
		errno = EINVAL;
		return -1;
	}

	ret = fgetxattr(fd, AFS_XATTR_PREFIX "fid", buf, sizeof(buf) - 1);
	if (ret == -1)
		return ret;

	buf[23] = 0;
	if (sscanf(buf, "%x:%x:%x", &fid->vid, &fid->vnode, &fid->unique) != 3) {
		errno = EIO;
		return -1;
	}

	args->out_size = sizeof(struct afs_fid);
	return 0;
}

/*
 * Get the cell that the file belongs to
 */
static long afs_PGetFileCell(int fd, int cmd, struct ViceIoctl *args)
{
	long ret;

	if (!args->out) {
		errno = EINVAL;
		return -1;
	}
	if (args->out_size < 2)
		goto overflow;

	ret = fgetxattr(fd, AFS_XATTR_PREFIX "cell", args->out, args->out_size - 1);
	if (ret == -1)
		return ret;
	if (ret > args->out_size - 1)
		goto overflow;
	args->out[ret] = 0;
	args->out_size = ret + 1;
	return 0;

overflow:
	errno = EINVAL;
	return -1;
}

/*
 * Get the volume status for the special pathname
 */
static long afs_PGetVolStat(int fd, int cmd, struct ViceIoctl *args)
{
	struct VolumeStatus vs;
	struct afs_fid fid;
	char cellname[AFSNAMEMAX];
	char pathname[256];
	char buf[8*3];
	char vs_buffer[AFSOPAQUEMAX];
	int ret, filed;
	FILE *f;
	char *p, *bp;

	if (args->out_size < sizeof(struct VolumeStatus) || !args->out) {
		errno = EINVAL;
		return -1;
	}

	ret = fgetxattr(fd, AFS_XATTR_PREFIX "fid", buf, sizeof(buf) - 1);
	if (ret == -1)
		return ret;

	buf[23] = 0;
	if (sscanf(buf, "%x:%x:%x", &fid.vid, &fid.vnode, &fid.unique) != 3) {
		errno = EIO;
		return -1;
	}

	ret = fgetxattr(fd, AFS_XATTR_PREFIX "cell", cellname, AFSNAMEMAX - 1);
	if (ret == -1)
		return ret;

	cellname[ret] = '\0';

	ret = sprintf(pathname, "/proc/fs/afs/%s/%08x/volstatus", cellname,
			fid.vid);
	if (ret == -1) {
		errno = ENODEV;
		return ret;
	}

	f = fopen(pathname, "r");
	if (!f)
		return -1;

	/* get the first line */
	if (!fgets(vs_buffer, AFSOPAQUEMAX, f))
		goto vs_err;

	/* get the volume status line */
	if (!fgets(vs_buffer, AFSOPAQUEMAX, f))
		goto vs_err;

	ret = sscanf(vs_buffer, "%x %x %u %u %u %u %u %u %u %u %u %u",
		     &vs.Vid, &vs.ParentId, (unsigned int *)&vs.Online,
		     (unsigned int *)&vs.InService, (unsigned int*)&vs.Blessed,
		     (unsigned int *)&vs.NeedsSalvage, &vs.Type, &vs.MinQuota,
		     &vs.MaxQuota, &vs.BlocksInUse, &vs.PartBlocksAvail,
		     &vs.PartMaxBlocks);
	if (ret != 12) {
		errno = EIO;
		return -1;
	}

	memcpy(args->out, &vs, sizeof(struct VolumeStatus));
	p = args->out + sizeof(struct VolumeStatus);

	/* get the volume name */
	if (!fgets(vs_buffer, AFSOPAQUEMAX, f))
		goto vs_err;

	bp = strchr(vs_buffer, ':');
	if (!bp)
		goto vs_err;
	if (strlen(++bp) > args->out_size - (p - args->out))
		goto vs_out;

	strcpy(p, bp);
	p += strlen(bp);
	*(p - 1) = '\0';

	if (fclose(f) == EOF)
		return -1;

	/* get the offline message name */
	ret = sprintf(pathname, "/proc/fs/afs/%s/%08x/offlinemsg", cellname,
			fid.vid);
	if (ret == -1) {
		errno = ENODEV;
		return ret;
	}

	filed = open(pathname, O_RDWR);
	if (filed == -1)
		return -1;

	if (read(filed, vs_buffer, AFSOPAQUEMAX) <= 0)
		goto msg_err;

	if (strlen(vs_buffer) + 1 > args->out_size - (p - args->out))
		goto msg_out;

	strcpy(p, vs_buffer);
	p += strlen(vs_buffer) + 1;

	if (close(filed) == -1)
		return -1;

	/* get the MOTD message name */
	ret = sprintf(pathname, "/proc/fs/afs/%s/%08x/motd", cellname,
			fid.vid);
	if (ret == -1) {
		errno = ENODEV;
		return ret;
	}

	filed = open(pathname, O_RDWR);
	if (fd == -1)
		return -1;

	if (read(filed, vs_buffer, AFSOPAQUEMAX) <= 0)
		goto msg_err;

	if (strlen(vs_buffer) + 1 > args->out_size - (p - args->out))
		goto msg_out;

	strcpy(p, vs_buffer);
	p += strlen(vs_buffer) + 1;

msg_out:
	args->out_size = p - args->out;
	if (close(filed) == -1)
		return -1;
	return 0;

msg_err:
	close(filed);
	return -1;

vs_out:
	args->out_size = p - args->out;
	if (fclose(f) == EOF)
		return -1;
	return 0;

vs_err:
	fclose(f);
	return -1;
}

/*
 * Get the volume status for the special pathname
 */
static long afs_PSetVolStat(int fd, int cmd, struct ViceIoctl *args)
{
	struct afs_fid fid;
	char cellname[AFSNAMEMAX];
	char pathname[256];
	char buf[8*3];
	int ret;
	int f;

	if (args->out_size < sizeof(struct VolumeStatus) || !args->out) {
		errno = EINVAL;
		return -1;
	}

	ret = fgetxattr(fd, AFS_XATTR_PREFIX "fid", buf, sizeof(buf) - 1);
	if (ret == -1)
		return ret;

	buf[23] = 0;
	if (sscanf(buf, "%x:%x:%x", &fid.vid, &fid.vnode, &fid.unique) != 3) {
		errno = EIO;
		return -1;
	}

	ret = fgetxattr(fd, AFS_XATTR_PREFIX "cell", cellname, AFSNAMEMAX - 1);
	if (ret == -1)
		return ret;

	cellname[ret] = '\0';

	ret = sprintf(pathname, "/proc/fs/afs/%s/%08x/volstatus", cellname,
			fid.vid);
	if (ret == -1) {
		errno = EIO;
		return ret;
	}

	f = open(pathname, O_RDWR);
	if (f == -1)
		return -1;

	/* write the new volume status */
	if (write(f, args->in, args->in_size) == -1)
		goto error_out;

	if (close(f) == -1)
		return -1;

	return 0;

error_out:
	close(f);
	return -1;
}

/*
 * the main pioctl multiplexor
 */
static long pioctl(const char *path, int cmd, struct ViceIoctl *args,
		   int follow)
{
	long ret;
	int fd, ret2;

	//fprintf(stderr, "\npioctl(%s,%x,%p,%d)\n", path ?: "", _IOC_NR(cmd), args, follow);

	if (args->in_size < 0 || args->out_size < 0)
		goto invalid_arg;

	/* handle pathless pioctls */
	switch (cmd) {
	case VIOCSETTOK:
		return afs_PSetTokens(args);
	case VIOCGETTOK:
		return afs_PGetTokens(args);
	default:
		if (!path) {
			errno = EINVAL;
			return -1;
		}
		break;
	}

	fd = open(path, O_NODE | O_NOACCESS | (follow ? 0 : O_NOFOLLOW), 0);
	if (fd == -1)
		return -1;

	switch (cmd) {
	case VIOCGETFID:
		ret = afs_PGetFID(fd, cmd, args);
		break;
	case VIOC_FILE_CELL_NAME:
		ret = afs_PGetFileCell(fd, cmd, args);
		break;
	case VIOCGETVOLSTAT:
		ret = afs_PGetVolStat(fd, cmd, args);
		break;
	case VIOCSETVOLSTAT:
		ret = afs_PSetVolStat(fd, cmd, args);
		break;
	case VIOCWHEREIS:
	case VIOCFLUSHCB:
	case VIOCAFSSTATMTPT:
		ret = ioctl(fd, cmd, args);
		break;
	default:
		errno = EOPNOTSUPP;
		ret = -1;
		break;
	}

	ret2 = close(fd);
	if (ret != -1 && ret2 == -1)
		ret = ret2;
	return ret;

invalid_arg:
	errno = EINVAL;
	return -1;
}

/*
 * the AFS system call multiplexor
 */
static long afs(long function, long b, long c, long d, long e, long f)
{
	if (function == AFS_PIOCTL) {
		return pioctl((const char *) b,
			      (int) c,
			      (struct ViceIoctl *) d,
			      (int) e);
	} else {
		printf("afs(%ld,%lx)\n", function, b);
		errno = ENOSYS;
		return -1;
	}
}

/*
 * override for the syscall() generic system call maker, using ELF magic to
 * give the kernel first go at processing the request
 */
long syscall(int number, long a, long b, long c, long d, long e, long f)
{
	static long (*libc_syscall)(int number,
				    long a, long b, long c,
				    long d, long e, long f);
	long ret;

	if (!libc_syscall) {
		libc_syscall = dlsym(RTLD_NEXT, "syscall");
		if (!libc_syscall) {
			fprintf(stderr, "Cannot look up syscall: %s\n",
				dlerror() ?: "unknown error");
			abort();
		}
	}

	ret = libc_syscall(number, a, b, c, d, e, f);
	if (ret != -1)
		return ret;

	if (errno == ENOSYS && number == SYS_afs_syscall)
		return afs(a, b, c, d, e, f);

	return -1;
}
